package com.tianji.promotion.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.promotion.OrderCourseDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.util.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class DiscountServiceImpl implements IDiscountService {
    private final CouponServiceImpl couponService;
    private final UserCouponServiceImpl userCouponService;
    private final CouponScopeServiceImpl couponScopeService;
    private final Executor calculateDiscountExecutor;

    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        Long userId = UserContext.getUser();
        if (userId == null) throw new RuntimeException("用户未登录");
        int totalPrice = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        //获取用户拥有的优惠卷
        List<UserCoupon> userCouponList = userCouponService.lambdaQuery()
                .select(UserCoupon::getCouponId)
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, UserCouponStatus.UNUSED)
                .list();
        if (userCouponList.isEmpty()) return Collections.emptyList();
        //获取用户拥有优惠券详细信息
        List<Long> userCouponIds = userCouponList.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        List<Coupon> couponList = couponService.lambdaQuery()
                .in(Coupon::getId, userCouponIds)
                .list();
        log.info("用户优惠券：{}", couponList.stream().map(Coupon::getName).collect(Collectors.toList()));
        //根据价格进行初始筛选
        List<Coupon> firstFilterCouponList = couponList
                .stream()
                .filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalPrice, coupon))
                .collect(Collectors.toList());
        log.info("初筛可用用户优惠券：{}", firstFilterCouponList.stream().map(Coupon::getName).collect(Collectors.toList()));
        if (firstFilterCouponList.isEmpty()) return Collections.emptyList();

        //根据优惠卷可用范围以及对应价格细筛
        //找出对于所购课程，每个优惠卷的可用课程
        HashMap<Long, List<OrderCourseDTO>> couponIdCourseScopeMap = new HashMap<>();
        for (Coupon coupon : firstFilterCouponList) {
            if (!coupon.getSpecific()) {
                couponIdCourseScopeMap.put(coupon.getId(), orderCourses);
                continue;
            }
            List<Long> courseIds = couponScopeService.lambdaQuery()
                    .select(CouponScope::getBizId)
                    .eq(CouponScope::getCouponId, coupon.getId())
                    .list()
                    .stream()
                    .map(CouponScope::getBizId)
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(courseIds)) couponIdCourseScopeMap.put(coupon.getId(), Collections.emptyList());

            //找出在这范围内的课程，也可以用filter
            List<OrderCourseDTO> availabeCourseIds = orderCourses.stream()
                    .filter(orderCourseDTO -> courseIds.contains(orderCourseDTO.getCateId()))
                    .collect(Collectors.toList());
            couponIdCourseScopeMap.put(coupon.getId(), availabeCourseIds);
        }
        //细筛
        List<Coupon> avaliableCouponList = firstFilterCouponList.stream().filter(coupon -> {
                    List<OrderCourseDTO> availabeCourseIds = couponIdCourseScopeMap.get(coupon.getId());
                    //可用课程为空数组，直接过滤
                    if (CollUtil.isEmpty(availabeCourseIds)) return false;
                    int avaliableTotalPrice = availabeCourseIds.stream().mapToInt(OrderCourseDTO::getPrice).sum();
                    return DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(avaliableTotalPrice, coupon);
                })
                .collect(Collectors.toList());
        log.info("细筛可用用户优惠券：{}", avaliableCouponList.stream().map(Coupon::getName).collect(Collectors.toList()));
        if (avaliableCouponList.isEmpty()) return Collections.emptyList();

        //排列组合优惠卷所有情况，计算每种优惠卷的优惠金额
        List<List<Coupon>> permute = PermuteUtil.permute(avaliableCouponList);
        //循环二维数组日志每种排列组合id情况
        log.info("优惠卷排列组合：{}", permute.stream().map(coupons ->
                coupons.stream().map(Coupon::getName).collect(Collectors.joining(","))
        ).collect(Collectors.toList()));
        //添加单卷方案
        for (Coupon coupon : avaliableCouponList) {
            permute.add(List.of(coupon));
        }
        List<CouponDiscountDTO> list = Collections.synchronizedList(new ArrayList<>(permute.size()));
        //计算每种情况优惠金额

//        for (List<Coupon> solution : permute) {
//            list.add(calculateSolutionDiscount(couponIdCourseScopeMap, orderCourses, solution));
//        }


        //多线程优化计算
        //生成计数器门阀阻塞主线程
        CountDownLatch countDownLatch = new CountDownLatch(permute.size());
        System.out.println(LocalDateTime.now());
        for (List<Coupon> solution : permute) {
            //循环开启异步任务
            CompletableFuture
                    .supplyAsync(() -> {
                        //log.info("线程{}开始计算方案{}", Thread.currentThread().getName(), solution.stream().map(Coupon::getName).collect(Collectors.toList()));
                        return calculateSolutionDiscount(couponIdCourseScopeMap, orderCourses, solution);
                    }, calculateDiscountExecutor)
                    .thenAccept(result -> {
                        list.add(result);
                        countDownLatch.countDown();
                    });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(LocalDateTime.now());
        log.info("优惠卷优惠方案：{}", list);
        //获取最优解
        //相等卷数，最高优惠
        //相等金额，最少卷数
        return findBestSolution(list);
    }

    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> list) {
        // 1.准备Map记录最优解
        Map<String, CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        Map<Integer, CouponDiscountDTO> lessCouponMap = new HashMap<>();
        // 2.遍历，筛选最优解
        for (CouponDiscountDTO solution : list) {
            // 2.1.计算当前方案的id组合
            String ids = solution.getIds().stream()
                    .sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            // 2.2.比较用券相同时，优惠金额是否最大
            CouponDiscountDTO best = moreDiscountMap.get(ids);
            if (best != null && best.getDiscountAmount() >= solution.getDiscountAmount()) {
                // 当前方案优惠金额少，跳过
                continue;
            }
            // 2.3.比较金额相同时，用券数量是否最少
            best = lessCouponMap.get(solution.getDiscountAmount());
            int size = solution.getIds().size();
            if (size > 1 && best != null && best.getIds().size() <= size) {
                // 当前方案用券更多，放弃
                continue;
            }
            // 2.4.更新最优解
            moreDiscountMap.put(ids, solution);
            lessCouponMap.put(solution.getDiscountAmount(), solution);
        }
        // 3.求交集
        Collection<CouponDiscountDTO> bestSolutions = CollUtils
                .intersection(moreDiscountMap.values(), lessCouponMap.values());
        // 4.排序，按优惠金额降序
        return bestSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }

    private CouponDiscountDTO calculateSolutionDiscount(HashMap<Long, List<OrderCourseDTO>> couponIdCourseScopeMap, List<OrderCourseDTO> orderCourses, List<Coupon> solution) {
        // 初始化折扣明细的映射
        Map<Long, Integer> detailMap = orderCourses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));
        //叠加该方案优惠金额
        int discountAmount = 0;
        for (Coupon coupon : solution) {
            //当前优惠卷可用课程
            List<OrderCourseDTO> orderCourseDTOS = couponIdCourseScopeMap.get(coupon.getId());
            //计算课程总价(课程原价 - 折扣明细)
            int totalPrice = orderCourseDTOS.stream()
                    .mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();
            //判读 基于前面的优惠后，该卷还是否可用
            if (!DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalPrice, coupon)) continue;

            //计算优惠金额
            int calculateDiscount = DiscountStrategy.getDiscount(coupon.getDiscountType()).calculateDiscount(totalPrice, coupon);
            discountAmount += calculateDiscount;
            // xxxxxxxxxxx修改OrderCourseDTO商品价格，方便后续优惠卷进行计算xxxxxxxx
            //更新折扣明细
            int size = orderCourseDTOS.size();
            int i = 1;
            for (OrderCourseDTO orderCourseDTO : orderCourseDTOS) {
                Long id = orderCourseDTO.getId();
                if (i != size) {
                    detailMap.put(id, detailMap.get(id) + calculateDiscount / size);
                    i++;
                } else
                    detailMap.put(id, detailMap.get(id) + (calculateDiscount - calculateDiscount * (size - 1) / size));
            }
        }
        CouponDiscountDTO couponDiscountDTO = new CouponDiscountDTO();
        couponDiscountDTO.setDiscountAmount(discountAmount);
        couponDiscountDTO.setIds(solution.stream().map(Coupon::getId).collect(Collectors.toList()));
        couponDiscountDTO.setRules(solution.stream().map(c -> DiscountStrategy.getDiscount(c.getDiscountType()).getRule(c)).collect(Collectors.toList()));
        return couponDiscountDTO;
    }
}
